package at.donalfonso.kellner;

import java.io.Serializable;
import java.util.List;
import java.util.Random;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import at.donalfonso.model.Bestellung;
import at.donalfonso.model.BestellungStatus;
import at.donalfonso.model.Gruppe;
import at.donalfonso.model.GruppeStatus;
import at.donalfonso.model.LieferBestellung;
import at.donalfonso.model.LieferKunde;
import at.donalfonso.model.LieferKundeStatus;
import at.donalfonso.model.Pizza;
import at.donalfonso.model.Pizza.PizzaEnum;
import at.donalfonso.model.Tisch;
import at.donalfonso.model.Tisch.TischStatus;
import at.donalfonso.utility.JMSUtility;

public class KellnerJMS implements MessageListener{

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private String name = "K1";

	private JMSUtility jmsSync;
	private JMSUtility jmsAsync;

	private JMSUtility jmsUtility = null;
	
	public int filiale;
	
	public static void main(String [] args){
		KellnerJMS kellner;
		int filiale = 1;
		
		if (args.length < 2) { 
			System.out.println("Usage: java KellnerJMS <ID> <Filiale>");
			Random random = new Random();
			kellner = new KellnerJMS("K" + random.nextInt(100), 1);
		} 
		else {
			kellner = new KellnerJMS(args[0], Integer.parseInt(args[1]));
			filiale = Integer.parseInt(args[1]);
			System.out.println("filiale: "+filiale);
		}

		kellner.initListener();
		
		try {
			JMSUtility jms = new JMSUtility(filiale);
			jms.getConnection().start();
			Session session = jms.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
			MessageConsumer cons = session.createConsumer(jms.getQueueEingang());

			while (true) {

				Thread.sleep(500);
				Message msg = cons.receive(500);
				
				if (msg != null) {
					
					System.err.println("betreueEingang() außerhalb des MessageListeners");
					System.out.println("msg: "+msg.getJMSDestination());
					
					
					/* Im Eingang können sich Gruppen und Lieferkunden befinden */
					if (msg instanceof ObjectMessage){
						
						ObjectMessage obj = (ObjectMessage) msg;
						Serializable ser = obj.getObject();
						//cons.close();
						//session.close();
						
						if (ser instanceof LieferKunde){
							kellner.lieferBestellungAufnehmen((LieferKunde) ser);
						}else{
							kellner.betreueEingang(msg);		
						}
						
						//session = jms.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
						//cons = session.createConsumer(jms.getQueueEingang());
					}
				}
			}

		} catch (JMSException | InterruptedException e) {
			e.printStackTrace();
		}

	}

	public KellnerJMS(String kellnerName, int filiale) {
		
		name = kellnerName;
		this.filiale = filiale;
		
		jmsSync = new JMSUtility(filiale);
		jmsAsync = new JMSUtility(filiale);
	}

	public void initListener(){
		System.out.println("initListner");
		Queue queue;
		MessageConsumer consumer;

		try {

			queue = jmsAsync.getQueueEingang();
			
			// Wenn die Session eine Transaktion enthält muss sie auch committed werden (in der Methode onMessage)
			// Sonst werden die Objekte nicht entnommen und verbleiben in der Queue Eingang. Daher ist es einfacher
			// eine implizite Transaktion für den Listener anzunehmen.
			//Session sess = jmsAsync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
			Session sess = jmsAsync.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
			consumer = sess.createConsumer(queue);
			consumer.setMessageListener(this);
			logger.debug("QueueEingang: Listener gestartet.");
			System.out.println("queue eingang: "+queue);

			queue = jmsAsync.getQueueBestellungFertig();
			//sess = jmsAsync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
			sess = jmsAsync.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
			
			// Der Kellner soll aus der Queue BestellungFertig nur die Bestellungen und nicht die Lieferbestellungen lesen.
			String selector = "Class = '"+Bestellung.class.getSimpleName()+"'"; 
			consumer = sess.createConsumer(queue, selector);
			consumer.setMessageListener(this);
			logger.debug("QueueBestellungFertig: Listener gestartet.");
			
			
			queue = jmsAsync.getQueueLieferung();
			consumer = jmsAsync.getSession().createConsumer(queue);
			consumer.setMessageListener(this);
			logger.debug("QueueLieferung: Listener gestartet.");
			
			//sess.commit();
			
			queue = jmsAsync.getQueueSaal();
			//sess = jmsAsync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
			sess = jmsAsync.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
			
			// MessageListener für Saal soll nur für Gruppen anspringen, Tische und Bestellungen
			// werden explizit aus QueueSaal gelesen
			// für Gruppen mit Status Warten, Bestellt oder Essen soll er ebenfalls nicht feuern, diese handeln selbständig 
			selector = "Class = '" + Gruppe.class.getSimpleName() + "' AND ( GruppeStatus = '" +
					GruppeStatus.SITZEN.toString() + "' OR GruppeStatus = '" + 
					GruppeStatus.BEZAHLEN.toString() + "' )";
			consumer = sess.createConsumer(queue, selector);
			consumer.setMessageListener(this);
			logger.debug("QueueSaal: Listener gestartet.");

			jmsAsync.getConnection().start();
			jmsSync.getConnection().start();
			System.out.println("waiting ...");

		} catch (JMSException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Weist wartenden Gästegruppen Tische zu.
	 * @throws JMSException 
	 */
	private void betreueEingang(Message msg1) throws JMSException{

		logger.debug("betreue Eingang");
		System.out.println("betreue Eingang");
		
		Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
		String selector = "Class = '" + Tisch.class.getSimpleName() + 
				"' AND TischStatus = '" + TischStatus.FREI + "'";

		MessageConsumer consTisch = sess.createConsumer(jmsSync.getQueueSaal(), selector);
		MessageProducer prod = sess.createProducer(null);
		
		Message msg2 = consTisch.receive(100);
		consTisch.close();
		
		if (msg1 instanceof ObjectMessage && msg2 instanceof ObjectMessage) {

			ObjectMessage om1 = (ObjectMessage) msg1;
			Serializable s1 = om1.getObject();
			ObjectMessage om2 = (ObjectMessage) msg2;
			Serializable s2 = om2.getObject();

			if (s1 instanceof Gruppe && s2 instanceof Tisch) {
				Gruppe g = (Gruppe) s1;
				Tisch t = (Tisch) s2;

				t.setGruppeId(g.getId());
				t.setStatus(TischStatus.BESETZT);
//				g.setStatus(GruppeStatus.SITZEN);
				g.setTischzuweisung(this.name);
				g.setTischId(t.getId());

				ObjectMessage om3 = sess.createObjectMessage(g);
				om3.setStringProperty("Class", g.getClass().getSimpleName());
				om3.setStringProperty("ID", g.getId());
				om3.setStringProperty("GruppeStatus", g.getStatus().toString());
				prod.send(jmsSync.getQueueSaal(), om3);
				
				ObjectMessage om4 = sess.createObjectMessage(t);
				om4.setStringProperty("Class", t.getClass().getSimpleName());
				om4.setStringProperty("ID", t.getId());
				om4.setStringProperty("TischStatus", t.getStatus().toString());
				prod.send(jmsSync.getQueueSaal(), om4);
				
				updateGui(sess, t);
				updateGui(sess, g);

			}else {
				
				sess.rollback();
				logger.warn("Rollback 1");
				logger.warn("schreibe Gruppe in Eingang zurück");
				prod.send(jmsSync.getQueueEingang(), msg1);
			}
		} else {
			sess.rollback();
			logger.warn("Rollback 2");
			logger.warn("kein Tisch frei, schreibe Gruppe in Eingang zurück");
			prod.send(jmsSync.getQueueEingang(), msg1);
		}
		prod.close();
		sess.commit();
		sess.close();
	}
	
	
	public void bestellungAufnehmen(Gruppe gruppe) throws JMSException {
		
		Bestellung bestellung = getNextBestellung();
System.out.println("BestellungAufnehmen: "+bestellung.getBestellungId());
		
		Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
		MessageProducer prod = sess.createProducer(null);
		
		List<PizzaEnum> pizzen = gruppe.getPizzen();
		
			
		// Zu jeder Bestellung wird die ID der Gruppe gespeichert.
		bestellung.addPizzen(pizzen);
		bestellung.setGruppenId(gruppe.getId());
		bestellung.setTischZuweisung(gruppe.getTischzuweisung());
		bestellung.setBestellKellner(this.name);
		
		ObjectMessage omb = sess.createObjectMessage(bestellung);
		omb.setStringProperty("ID", bestellung.getBestellungId());
		
		// Der Kellner schreibt die aufgenommene Bestellung in die Queue BestellungAufgenommen.
		// Der Koch schreibt sie angschließend in die Queue BestellungOffen und nimmt die Pizzen auf.
		Queue queue = jmsSync.getQueueBestellungAufgenommen();
		prod.send(queue, omb);
		updateGui(sess, bestellung);
		System.out.println("schreibe Bestellung ("+bestellung.getBestellungId()+") in queue "+queue);
		
		updateGui(sess, bestellung);
		
		gruppe.setStatus(GruppeStatus.BESTELLT);
		ObjectMessage omg = sess.createObjectMessage(gruppe);
		omg.setStringProperty("Class", gruppe.getClass().getSimpleName());
		omg.setStringProperty("ID", gruppe.getId());
		omg.setStringProperty("GruppeStatus", gruppe.getStatus().toString());
		prod.send(jmsSync.getQueueSaal(), omg);
		
		updateGui(sess, gruppe);
		
		prod.close();
		sess.commit();
		sess.close();
//		logger.debug("Bestellung in BestellungOffen geschrieben: " + bestellung);

	}

	public Bestellung getNextBestellung(){
		
		Bestellung bestellung = null;
		
		try{
			Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
			
			MessageConsumer cons = sess.createConsumer(jmsSync.getQueueBestellungNeu());
			Message msgBest = cons.receive(2000);
			cons.close();
			
			if (msgBest instanceof ObjectMessage) {
				Serializable s = ((ObjectMessage) msgBest).getObject();
				if (s instanceof Bestellung) {
					bestellung = (Bestellung) s;
					logger.debug("neue Bestellung erfolgreich gelesen: " + bestellung);
				}
				else {
					logger.error("Bestellung erwartet, erhalten: " + s);
					sess.rollback();
					return null;
				}
			}
			else {
				logger.error("ObjectMessage erwartet, erhalten: " + msgBest);
				sess.rollback();
				return null;
			}
			System.out.println("getNextBestellung: "+bestellung.getBestellungId());
			sess.commit();
			sess.close();
			
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
		return bestellung;
	}
	
/*
  	public void lieferBestellungAufnehmen(LieferBestellung bestellung){
 
		try{
			
			bestellung.setLieferstatus(LieferBestellungStatus.BESTELLT);
			bestellung.setBestellKellner(name);
			
			System.out.println("lieferBestellungAufnehmen: "+bestellung);
			
			Queue queueLieferung = getJMSUtility().getQueueBestellungOffen();
			MessageProducer producer = getJMSUtility().getSession().createProducer(queueLieferung);
			Message msg = getJMSUtility().getSession().createObjectMessage(bestellung);
			msg.setStringProperty("ID", bestellung.getBestellungId());
			producer.send(msg);
			
			updateGui(getJMSUtility().getSession(), bestellung);
			
			// Pizzen in die Queue stellen.
			Queue queue = getJMSUtility().getQueueKuecheInput();
			producer = getJMSUtility().getSession().createProducer(queue);
			
			for(Pizza pizza: bestellung.getPizzen()){
				pizza.setBestellnummer(bestellung.getBestellungId());
				msg = getJMSUtility().getSession().createObjectMessage(pizza);
				msg.setStringProperty("ID", pizza.getId());
				producer.send(msg);
			}			
		
		} catch (JMSException e) {
			e.printStackTrace();
		}
		
	}
*/
	
	public void lieferBestellungAufnehmen(LieferKunde kunde) throws JMSException {
		System.out.println("lieferBestellungAufnehmen: "+kunde);
		
		Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
		MessageProducer prod = sess.createProducer(null);
	
		Bestellung bestellung = getNextBestellung();
		
		// Muss explizit Lieferbestellung sein (statt Bestellung) da sonst der Fahrer nicht darauf reagieren kann.		
		LieferBestellung lieferBestellung = new LieferBestellung(filiale);
		lieferBestellung.setBestellungId(bestellung.getBestellungId());
		
		List<PizzaEnum> pizzen = kunde.getPizzen();
					
		// Zu jeder Bestellung wird die ID der Gruppe gespeichert.
		lieferBestellung.addPizzen(pizzen);
		lieferBestellung.setAdresseExistiert(kunde.adresseExistiert());
		lieferBestellung.setGruppenId(kunde.getId());
		lieferBestellung.setBestellKellner(this.name);
	
		ObjectMessage omb = sess.createObjectMessage(lieferBestellung);
		omb.setStringProperty("ID", lieferBestellung.getBestellungId());
		omb.setStringProperty("CLASS", "lieferBestellung"); // Damit der Loadbalancer gezielt Lieferbestellungen behandeln kann.
		Queue queue = jmsSync.getQueueBestellungAufgenommen();
		prod.send(queue, omb);
		System.out.println("schreibe lieferBestellung ("+lieferBestellung.getBestellungId()+") in queue "+queue);
		
		
		// Für den Loadbalancer wird die Lieferbestellung auch in das Topic Lieferung geschrieben.
		omb = sess.createObjectMessage(lieferBestellung);
		omb.setStringProperty("STATUS", "NEU");
		prod.send(jmsSync.getTopicLieferung(), omb);
				
		updateGui(sess, lieferBestellung);
		
		prod.close();
		sess.commit();
		sess.close();
	}
	
	/**
	 * Die Bestellung ist fertig und soll zum Gast gebracht werden.
	 * @param bestellung
	 */
	public void bestellungFertig(Bestellung bestellung) throws JMSException {

		List<Pizza> pizzen = bestellung.getPizzen();

		Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
		Queue queue = jmsSync.getQueueKuecheOutput();

		for(Pizza pizza: pizzen){
			String selector = "ID = '"+pizza.getId()+"'";
			
			Session session = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
			
			MessageConsumer consumer = session.createConsumer(queue, selector);
			Message message = consumer.receive(); 

			// Pizza an Gast übergeben.
			updateGui(session, pizza);
		}
		logger.debug("Pizzen aus Kueche_Output geholt.");
		
		// Bestellung aktualisieren und in Queue Saal schreiben
		bestellung.setServierKellner(this.name);
		bestellung.setStatus(BestellungStatus.AUSGELIEFERT);
		updateGui(sess, bestellung);
		
		ObjectMessage omb = sess.createObjectMessage(bestellung);
		omb.setStringProperty("Class", bestellung.getClass().getSimpleName());
		omb.setStringProperty("ID", bestellung.getBestellungId());
		omb.setStringProperty("Gruppe", bestellung.getGruppenId());
		MessageProducer prod = sess.createProducer(jmsSync.getQueueSaal());
		prod.send(omb);
		logger.debug("ausgelieferte Bestellung aktualisiert: " + bestellung);

		String selector = "ID = '" + bestellung.getGruppenId() + "'";
		MessageConsumer consGruppe = sess.createConsumer(jmsSync.getQueueSaal(), selector);
		logger.debug("versuche Gruppe " + bestellung.getGruppenId() + " zu empfangen.");
		Message msg = consGruppe.receive();
		logger.debug("Gruppe gelesen, erneut schreiben.");
		
		if (msg instanceof ObjectMessage) {
			Serializable s = ((ObjectMessage) msg).getObject();
			if (s instanceof Gruppe) {
				Gruppe g = (Gruppe) s;
				g.setStatus(GruppeStatus.ESSEN);
				ObjectMessage omg = sess.createObjectMessage(g);
				omg.setStringProperty("Class", g.getClass().getSimpleName());
				omg.setStringProperty("ID", g.getId());
				omg.setStringProperty("GruppeStatus", g.getStatus().toString());
				prod.send(omg);
				
				updateGui(sess, g);
			}
		}
		
		consGruppe.close();
		prod.close();
		sess.commit();
		sess.close();
	}

	/**
	 * Eine Gruppe möchte bezahlen. 
	 * @param gruppeId
	 */
	public void bezahlen(Gruppe gruppe) throws JMSException {
		logger.debug("Gruppe möchte bezahlen.");

		Session sess = jmsSync.getConnection().createSession(true, Session.SESSION_TRANSACTED);
		
		String selector = "Gruppe = '" + gruppe.getId() + "'";
		MessageConsumer consBest = sess.createConsumer(jmsSync.getQueueSaal(), selector);
		Message msg = consBest.receive();
		logger.debug("zu zahlende Bestellung empfangen");
		
		if (msg instanceof ObjectMessage) {
			Serializable s = ((ObjectMessage) msg).getObject();
			if (s instanceof Bestellung) {
				Bestellung b = (Bestellung) s;
				b.setBezahlKellner(this.name);
				b.setStatus(BestellungStatus.BEZAHLT);
				updateGui(sess, b);
				logger.debug("Bestellung aktualisiert");
			}
		}

		logger.debug("Gruppe auf gegangen setzen");
		gruppe.setStatus(GruppeStatus.GEGANGEN);
//		Session sess = jmsSync.getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE);
		ObjectMessage omg = sess.createObjectMessage(gruppe);
		omg.setStringProperty("Class", gruppe.getClass().getSimpleName());
		omg.setStringProperty("ID", gruppe.getId());
		omg.setStringProperty("GruppeStatus", gruppe.getStatus().toString());
		MessageProducer prod = sess.createProducer(jmsSync.getQueueSaal());
		prod.send(omg);
//		sess.close();
		logger.debug("Gruppe aktualisiert: " + gruppe);
		
		updateGui(sess, gruppe);
		
		
		// Tisch freigeben, sollte eigentlich in GruppeThreadJMS stattfinden
		selector = "Class = '" + Tisch.class.getSimpleName() + "' AND ID ='" + gruppe.getTischId() + "'";
		MessageConsumer consTisch = sess.createConsumer(jmsSync.getQueueSaal(), selector);
		msg = consTisch.receive();
//		logger.debug("freizugebender Tisch empfangen: " + msg);
		if (msg instanceof ObjectMessage) {
			Serializable s = ((ObjectMessage) msg).getObject();
			if (s instanceof Tisch) {
				Tisch t = (Tisch) s;
				t.setGruppeId("");
				t.setStatus(TischStatus.FREI);
				ObjectMessage omt = sess.createObjectMessage(t);
				omt.setStringProperty("Class", t.getClass().getSimpleName());
				omt.setStringProperty("ID", t.getId());
				omt.setStringProperty("TischStatus", t.getStatus().toString());
				MessageProducer prodTisch = sess.createProducer(jmsSync.getQueueSaal());
				prodTisch.send(omt);
				prodTisch.close();
//				logger.debug("Tisch aktualisiert: " + t);
				updateGui(sess, t);
			}
		}
		
		consBest.close();
		consTisch.close();
		prod.close();
		sess.commit();
		sess.close();
		
		// Da jetzt ein Tisch frei ist, versuche betreueEingang()
		MessageConsumer cons = jmsSync.getSession().createConsumer(jmsSync.getQueueEingang());
		Message msgEingang = cons.receive(100);
		cons.close();
		if (msgEingang instanceof Message) {
			logger.debug("Gruppe am Eingang gefunden.");
			betreueEingang(msgEingang);
		}
	}
	
	@Override
	public void onMessage(Message arg0) {
		System.out.println("message erhalten: "+arg0);
		try {
			System.out.println("=>onMessage: "+arg0.getJMSDestination()+" : ");
//			Destination destination = arg0.getJMSDestination();

			if (arg0 instanceof ObjectMessage) {
				Serializable s = ((ObjectMessage) arg0).getObject();

				if (s instanceof Bestellung) {
				
					Bestellung bestellung = (Bestellung) ((ObjectMessage) arg0).getObject();
					logger.debug("onMessage " + name + ": Bestellung " + bestellung);
					if (bestellung.getStatus() == BestellungStatus.BESTELLT)
						/* Eine Bestellung ist fertig und kann zum Gast gebracht werden */
						bestellungFertig(bestellung);

//					updateGui(bestellung);
					
				}
				else if (s instanceof LieferKunde) {
					System.out.println("Lieferkunde erhalten aus queue: "+arg0.getJMSDestination());
					lieferBestellungAufnehmen((LieferKunde) s);			
				}
				else if (s instanceof Gruppe) {
					Gruppe g = (Gruppe) s;
					logger.debug("onMessage " + name + ": Gruppe " + g);
				
					if (g.getStatus() == GruppeStatus.WARTEN){
						System.out.println("Gruppe wartet im Eingang: "+g.getId());
						//&&
							//arg0.getJMSDestination().toString().equals("queue://" + JMSUtility.QUEUE_EINGANG)) 
						betreueEingang(arg0);
					}
								
					if (g.getStatus() == GruppeStatus.SITZEN)
						bestellungAufnehmen(g);
					
					if (g.getStatus() == GruppeStatus.BEZAHLEN)
						bezahlen(g);
					
				} else {
					logger.warn("Unerwartetes Objekt erhalten: " + s);
				}
			
			}

		} catch (JMSException e) {
			e.printStackTrace();
		}
		
	}	
	
	/**
	 * Über das Topic "Gui_update" werden Statusänderungen kommuniziert.
	 */
	public void updateGui(Session session, Serializable object){

//		logger.debug("Updating Gui.");
		//System.out.println("updateGui: " + object);

		try {
			Topic topic = jmsSync.getTopicGuiUpdate();
			Message msg = session.createObjectMessage(object);
			msg.setStringProperty("Class", object.getClass().getSimpleName());
			msg.setStringProperty("Sender", this.name);
			MessageProducer producer = session.createProducer(topic);

			producer.send(msg);

			producer.close();

		} catch (JMSException e) {

			logger.debug("GUI Update failed.");

			e.printStackTrace();
		}	
	}
	
	public JMSUtility getJMSUtility(){
		if (jmsUtility == null){
			jmsUtility = new JMSUtility(filiale);
		}
		return jmsUtility;
	}
}
